package com.beantools.factory.verify;

import com.beantools.entity.EntityStorage;
import com.beantools.execption.ClassException;
import com.beantools.execption.PropertyException;
import com.beantools.filters.BuilderFilters;
import com.beantools.util.CollectionUtil;
import com.beantools.util.StringUtil;
import com.beantools.util.poutil.PropertyUtil;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;

import static com.beantools.constants.RegexExpress.PROPERTY_REGEX;
import static com.beantools.constants.SingleChar.EN_POINT;
import static com.beantools.util.CollectionUtil.isEmpty;
import static com.beantools.util.ObjectUtil.*;
import static com.beantools.util.StringUtil.isNotBlank;

/**
 * 这是一个abstractVerifyFactory抽象类的继承类，主要用来校验实体对象的属性命名是否
 * 符合规范。比如在将属性名称转化为set方法时，如果遇到了单字母、首字母大写、首字母
 * 小写和第二个字母大写的问题，类似{@code public String nAme}这样的属性,程序终止将
 * 对象转化为json格式，同时这就需要抛出异常。
 *
 * @author baoya
 * @version 1.1.0
 * @Documented
 * @since 1.0
 */

public class StorageVerifyFactory extends AbstractVerifyFactory<EntityStorage> {


    private static final long serialVersionUID = -340838606390649731L;

    /**
     * 属性验证器的工厂类
     */
    private static StorageVerifyFactory propertyVerify;

    /**
     * 当前类的类字面常量，表示哪个类下面的属性
     */
    private Class<?> persistClass;

    /**
     * 私有方法的构造器，不允许外键通过构造器创建对象
     * 其有一个参数{@code persistClass}，表示当前实体类
     *
     * @param persistClass 当前实体类
     */
    private StorageVerifyFactory(Class<?> persistClass) {
        this.persistClass = persistClass;
    }


    private StorageVerifyFactory() {
    }


    /**
     * 采用单例设计模式，创建属性验证器地实例化对象；如果对象为空，则创建对象
     * 否则，返回已创建实例化带参对象
     *
     * @return 属性验证器地实例化对象
     * @throws IllegalArgumentException 参数非法，表示实体类为空的非法参数
     */
    public static StorageVerifyFactory getInstance(Class<?> persistClass) throws IllegalArgumentException {
        if (isNull(persistClass)) {
            throw new IllegalArgumentException("实体类不能为空");
        }
        propertyVerify = new StorageVerifyFactory(persistClass);
        return propertyVerify;
    }

    /**
     * 采用单例设计模式，创建属性验证器地实例化对象；如果对象为空，则创建对象
     * 否则，返回已创建实例化无参对象
     *
     * @return 属性验证器地实例化对象
     * @throws IllegalArgumentException 参数非法，表示实体类为空的非法参数
     */
    public static StorageVerifyFactory getInstance() {
        propertyVerify = new StorageVerifyFactory();
        return propertyVerify;
    }

    /**
     * 如果对象为空，直接返回false；获取对象的属性集合，在有属性的情况，
     * 判断属性的命名是否符合规则，不符合规则，直接返回false;
     * <p>
     * 如果当前对象存在父类，且不是Object类，需要判断父类的命名是否符合规则
     * <strong>正在开发中，尽量不要使用</strong>
     *
     * @param obj 待验证的对象
     * @return 符合规则，表示验证成功，返回true，否则，返回false
     * @author baoya
     */
    @Override
    @Deprecated
    public boolean verifySuccess(Object obj) {
        if (isNull(obj)) {
            return false;
        }
        return false;
    }

    /**
     * 使用该方法进行数据校验，如果校验成功，返回{@code true}，否则返回{@code false}
     *
     * @param coll 待校验的容器对象
     * @return 校验成功，返回true，否则返回false
     * @throws PropertyException java严格按照驼峰原则，属性首字母不能大写，
     *                           第二个字母也不能大写，否则就会抛出异常
     *                           属性只能是字母和数字组成，否则，就抛出异常
     */
    @Override
    public boolean verifySuccess(Collection<EntityStorage> coll) throws PropertyException {
        if (isEmpty(coll)) {
            return false;
        }
        boolean successValidate = true;
        for (EntityStorage property : coll) {
            String propertyName = property.getPropertyName();
            successValidate = verifyPropertyName(propertyName);
            if (false == successValidate) {
                break;
            }
        }
        return successValidate;
    }

    /**
     * 验证属性名是否符合规则
     *
     * @param propertyName 对象属性名
     * @return 校验成功，返回true，否则返回false
     * @throws IllegalArgumentException 参数异常
     * @throws PropertyException        java严格按照驼峰原则，属性首字母不能大写，
     *                                  第二个字母也不能大写，否则就会抛出异常
     *                                  属性只能是字母和数字组成，否则，就抛出异常
     */
    public boolean verifyPropertyName(String propertyName) throws IllegalArgumentException, PropertyException {
        boolean successValidate = PropertyUtil.beforeSecondLetterUppercase(propertyName);
        if (successValidate) {
            throw new PropertyException("首字母和第二个字母均不能大写", propertyName, persistClass);
        }
        boolean regexTrue = verifyPropertyRegex(propertyName);
        if (!regexTrue) {
            throw new PropertyException("出现了非法字符，属性只能是字母和数字组成", propertyName, persistClass);
        }
        return true;
    }

    /**
     * 这是一个静态方法，用来验证属性的非法字符。属性是由字母、数字、下划线构成的
     * 但是，为了满足驼峰规则，一般不用下划线，所以，属性只能是字母和数字。
     * 如果出现了非法字符，就验证不通过，这时，交给调用者来抛相应的异常信息。
     *
     * @param character 待验证的字符串
     * @return 如果验证成功，则返回true，验证不成，则返回false
     */
    public static boolean verifyPropertyRegex(String character) {
        return isNotBlank(character) ? verifyIllegalCharacter(character, PROPERTY_REGEX) : false;
    }

    /**
     * 这是存储实体类的方法。具体要分析propertyName实参数据。
     * <p>
     * 比如在{@code Student}类中，有个名为{@code name}的属性，
     * 可以通过getProperty("name",student)获取属性{@code name}值
     * <p>
     * 如果有{@code Course}的课程类，其有有属性为{@code student}
     * 可以通过getProperty("student.name",student)
     * <p>
     * 此时，调用者需要处理{@code student.name}这种格式的实参值
     *
     * @param object
     * @param clazz        当前类对象信息
     * @param propertyName 属性名称
     * @return 属性实体类
     */
    public EntityStorage getStorageEntity(Object object, Class clazz, String propertyName, String flag) {
        if (isAllNull(clazz)) {
            throw new ClassException("类字面常量不能为空");
        }
        if (StringUtil.isEmpty(propertyName)) {
            throw new PropertyException(null);
        }
        if (propertyName.indexOf(EN_POINT) == -1) {
            return getEntityStorage(object, clazz, propertyName, flag);
        }
        return containsPointStorage(object, clazz, propertyName, flag);
    }

    /**
     * 将类信息和对象封装到实体对象仓库中，从而，获取到更全面的仓库对象
     *
     * @param object       实体对象
     * @param propertyName 属性名称
     * @param clazz        实体类型信息
     * @return 返回实体库存对象
     */
    private EntityStorage getEntityStorage(Object object, Class clazz, String propertyName, String flag) {
        EntityStorage storage = storeEntity(clazz, propertyName, flag);
        storage.setClazz(clazz);
        storage.setObject(object);
        return storage;
    }

    /**
     * 这是用来处理类似于{@code student.name}这种数据格式的方法
     *
     * @param object       实体对象
     * @param propertyName 属性名称
     * @param clazz        实体类型信息
     * @param flag         过滤类型，type只能是 setter get方法
     * @return 返回实体库存对象
     */
    private EntityStorage containsPointStorage(Object object, Class clazz, String propertyName, String flag) {
        String propName = propertyName.substring(0, propertyName.indexOf(EN_POINT));
        if (StringUtil.isEmpty(propName)) {
            throw new PropertyException(null);
        }
        EntityStorage storage = storeEntity(clazz, propName, flag);
        Class type = storage.getPropertyType();
        try {
            Object invoke = storage.getMethod().invoke(object);
            if (isNull(invoke)) {
                return null;
            }
            propertyName = propertyName.substring(propertyName.indexOf(EN_POINT) + 1);
            return getStorageEntity(invoke, type, propertyName, flag);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 这是通过类对象，获取属性实体类的方法。如果调用者通过没有传递
     * 属性{@code persistClass}对象，那么就使用调用者传来的值。
     *
     * @param propertyName 属性名称
     * @param clazz        当前类对象信息
     * @param flag         过滤类型，type只能是 setter get方法
     * @return 属性实体类
     */
    public EntityStorage storeEntity(Class clazz, String propertyName, String flag) {
        if (isAllNull(clazz, this.persistClass)) {
            throw new ClassException("类字面常量不能为空");
        }
        clazz = isNotNull(clazz) ? clazz : this.persistClass;
        Method[] methods = clazz.getMethods();
        Set<EntityStorage> properties = BuilderFilters.builderMethodFilters().filter(methods, flag);
        if (CollectionUtil.isEmpty(properties)) {
            throw new ClassException("没有相应的" + flag + "方法", clazz);
        }
        return storeEntity(clazz, properties, propertyName);
    }

    /**
     * 这是一个获取属性对象的方法。通过当前类对象的属性实体和属性名称，
     * 过滤出当前属性对象信息。因为通过这种方式，可以获得父类对象的属性
     *
     * @param properties   当前类对象的属性实体
     * @param propertyName 属性名称
     * @return 返回的是属性对象的实体类
     */
    private EntityStorage storeEntity(Class clazz, Set<EntityStorage> properties, String propertyName) {
        EntityStorage property = null;
        boolean notEquals = true;
        if (CollectionUtil.notAnyNullObj(properties)) {
            for (EntityStorage propertyEntity : properties) {
                //                如果包含的话，就返回当前这个propertyEntity对象
                String propName = propertyEntity.getPropertyName();
                if (propertyName.equals(propName)) {
                    property = propertyEntity;
                    notEquals = false;
                    break;
                }
            }
        }
        if (notEquals) {
            throw new PropertyException("没有这样的属性", propertyName, clazz);
        }
        return property;
    }

}
